/*
Name: Ajinkya Arun Gurav 
Div : H  Roll No:21
GR No : 121237
Subject : Network Security Lab

Lab 2: Implement encryption and decryption using Hill Cipher.
 */

import java.io.*;
import java.util.Scanner;


public class HillCipher {

	public static void main(String args[])
	{
		String PlainText=null,CipherText=null;
		int key[][]=new int[2][2];
		BufferedReader br= new BufferedReader(new InputStreamReader(System.in));
		Scanner in = new Scanner(System.in);
		System.out.println("Enter the PlainText");
		try {
			PlainText=br.readLine();
			System.out.println("Enter the key matrix; 4 elements");
			 for(int i=0;i<2;i++)
			 {
				 for(int j=0;j<2;j++)
				 {
					 key[i][j]=in.nextInt();
				 }
			 }
			CipherText=encrypt(PlainText,key);
			System.out.println("Ciphertext is : "+CipherText);
			PlainText=decrypt(CipherText,key);
			System.out.println("PlainText is : "+PlainText);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			System.out.println("Exception is: "+e.getCause());
		}
	}
	static String encrypt(String ptext, int key[][] )
	{
		int p[][]=new int[1][2];
		int result[][]=new int[1][2];
		String temp="";
		 //HashMap<Character,Integer> hm=new HashMap<Character,Integer>();
		 /*for(int i=0;i<ptext.length();i++)
		 hm.put(ptext.charAt(i),i);  */
		try{
			p=make1Dmat(ptext);
			}
		catch(Exception e)
		{
			System.out.println("Exception is: "+e.getLocalizedMessage()+"-----"+e.getMessage());
		}
		result=MulMatrix(key,p);
		for(int j=0;j<result[0].length;j++)
		{
			result[0][j]=(result[0][j]%26);
			temp=temp+((char)(result[0][j]+97));
		}
		
		 /*for(Map.Entry m:hm.entrySet()){  
			   System.out.println(m.getKey()+" "+m.getValue());  
			  }  */ 
		 
		return temp;
	}
	
	static int[][] make1Dmat(String txt)
	{
		int [][] p=new int[1][txt.length()];
		for(int i=0;i<txt.length();i++)
		{
			p[0][i]=txt.charAt(i)-97;
		}
		return p;
	}
	
	static String decrypt(String ctext, int key[][] )
	{
		//System.out.println((-43)%26);
		int result[][]=new int[1][2];
		int InvKey[][]=new int[2][2];
		String temp="";
		
		InvKey=FindInverse(key);
		result=make1Dmat(ctext);
		result=MulMatrix(InvKey, result);
		for(int j=0;j<result[0].length;j++)
		{
			result[0][j]= (result[0][j]%26)< 0 ? 26 + (result[0][j]%26) : (result[0][j]%26) % 26;
			temp=temp+((char)(result[0][j]+97));
		}
		
		return temp;
	}
	
	static int[][] FindInverse(int[][] key)
	{
		int IKey[][]=new int[2][2];
		int temp=0;
		int det=0;
		int i=0;
		det=determinant(key);
		System.out.println("Determinant is : "+det);
		for(i=0;i<26;i++)
		{
			if((det*i)%26==1)
			{
				break;
			}
		}
		det=i-26;
		for(int y=0;y<2;y++)
		{
			for(int z=0;z<2;z++)
			{
				if(y==0 && z==0)
				{
					temp=0;
					IKey[y][z]=key[y][z];
					IKey[y+1][z+1]=key[y+1][z+1];
					temp=IKey[y][z];
					IKey[y][z]=IKey[y+1][z+1];
					IKey[y+1][z+1]=temp;
				}
				else if(y==0 && z==1)
				{
					IKey[y][z]=-key[y][z];
					IKey[y+1][z-1]=-key[y+1][z-1];
				}
				IKey[y][z]=(det*IKey[y][z])%26;
			}
		}
		return IKey;
	}
	
	static int determinant(int[][] matrix){ //method sig. takes a matrix (two dimensional array), returns determinant.
	{
		  int sum=0; 
		    int s;
		    if(matrix.length==1){  //bottom case of recursion. size 1 matrix determinant is itself.
		      return(matrix[0][0]);
		    }
		    for(int i=0;i<matrix.length;i++){ //finds determinant using row-by-row expansion
		      int[][]smaller= new int[matrix.length-1][matrix.length-1]; //creates smaller matrix- values not in same row, column
		      for(int a=1;a<matrix.length;a++){
		        for(int b=0;b<matrix.length;b++){
		          if(b<i){
		            smaller[a-1][b]=matrix[a][b];
		          }
		          else if(b>i){
		            smaller[a-1][b-1]=matrix[a][b];
		          }
		        }
		      }
		      if(i%2==0){ //sign changes based on i
		        s=1;
		      }
		      else{
		        s=-1;
		      }
		      sum+=s*matrix[0][i]*(determinant(smaller)); //recursive step: determinant of larger determined by smaller.
		    }
		    return(sum);
	}//returns determinant value. once stack is finished, returns final determinant.
	}
	
	static int[][] MulMatrix(int key[][],int p[][])
	{
		//int result[][]=new int[1][2];
		 int rowsInP = p.length;
	       int columnsInP = p[0].length; // same as rows in key
	       int columnsInKey = key[0].length;
	       int[][] result = new int[rowsInP][columnsInKey];
	       for (int i = 0; i < rowsInP; i++) {
	           for (int j = 0; j < columnsInKey; j++) {
	               for (int k = 0; k < columnsInP; k++) {
	                   result[i][j] = result[i][j] + p[i][k] * key[k][j];
	               }
	           }
	       }
		
		return result;
	}
	
}
/*
 *OUTPUT:
Enter the PlainText
me
Enter the key matrix; 4 elements
9
4
5
7
Ciphertext is : yy
Determinant is : 43
PlainText is : me

 */
